package com.example.bootintegration.datasource;

import com.example.bootintegration.util.PrefixedPropertyResolver;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.dialect.MySQLDialect;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.env.Environment;
import org.springframework.orm.hibernate5.SpringSessionContext;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import javax.sql.DataSource;
import java.util.Properties;

/**
 * @author Quintoy
 * @date 12/30/22
 */
@Component
public class JpaFactory implements ApplicationContextAware {
    private ApplicationContext applicationContext;
    private Environment env;

    public LocalContainerEntityManagerFactoryBean entityManagerFactory(String dataSourceName, DataSource dataSource) {
        LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
        emf.setPersistenceUnitName(dataSourceName);
        emf.setDataSource(dataSource);
        // emf.setJpaDialect(new HibernateJpaDialect());
        emf.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
        emf.setPackagesToScan(env.getProperty("boot.datasource." + dataSourceName + ".entity", "com.synnex.entityLostInParadise"));
        PrefixedPropertyResolver resolver = new PrefixedPropertyResolver(env, "boot.datasource." + dataSourceName + ".");
        emf.setJpaProperties(this.getJpaProperties(resolver));
        return emf;
    }

    public JpaTransactionManager transactionManager(String dataSourceName) {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setPersistenceUnitName(dataSourceName);
        return transactionManager;
    }

    /**
     * copy from snx-jpa
     * @param dsConfig
     * @return
     */
    private Properties getJpaProperties(PrefixedPropertyResolver dsConfig) {
        Properties result = new Properties();
        String jdbcUrl = dsConfig.getProperty("url");
        String dialect = dsConfig.getProperty("hibernate.dialect", this.getDefaultHibernateDialect(jdbcUrl));
        Assert.hasText(dialect, "com.config required: " + dsConfig.getPrefix() + "hibernate.dialect");
        result.setProperty("hibernate.dialect", dialect);
        result.setProperty("hibernate.jdbc.use_get_generated_keys", dsConfig.getProperty("hibernate.jdbc.use_get_generated_keys", this.isKeyGenSupport(jdbcUrl) + ""));
        result.setProperty("hibernate.current_session_applicationContext_class", SpringSessionContext.class.getName());
        result.setProperty("hibernate.show_sql", this.applicationContext.getEnvironment().getProperty("spring.jpa.show-sql", "true"));
        PrefixedPropertyResolver jpaConfig = new PrefixedPropertyResolver(this.applicationContext.getEnvironment(), "spring.jpa.properties.");
        result.setProperty("hibernate.format_sql", jpaConfig.getProperty("hibernate.format_sql", "false"));
        result.setProperty("hibernate.use_sql_comments", jpaConfig.getProperty("hibernate.use_sql_comments", "false"));
        result.setProperty("hibernate.generate_statistics", jpaConfig.getProperty("hibernate.generate_statistics", "false"));
        result.setProperty("hibernate.cache.use_second_level_cache", jpaConfig.getProperty("hibernate.cache.use_second_level_cache", "false"));
        result.setProperty("hibernate.cache.use_query_cache", jpaConfig.getProperty("hibernate.cache.use_query_cache", "false"));
        result.setProperty("hibernate.jdbc.fetch_size", jpaConfig.getProperty("hibernate.jdbc.fetch_size", "0"));
        result.setProperty("hibernate.jdbc.batch_size", jpaConfig.getProperty("hibernate.jdbc.batch_size", "30"));
        result.setProperty("hibernate.max_fetch_depth", jpaConfig.getProperty("hibernate.max_fetch_depth", "3"));
        result.setProperty("hibernate.connection.release_mode", jpaConfig.getProperty("hibernate.connection.release_mode", "auto"));
        result.setProperty("jakarta.persistence.validation.mode", jpaConfig.getProperty("jakarta.persistence.validation.mode", "none"));
        result.setProperty("hibernate.query.plan_cache_max_size", jpaConfig.getProperty("hibernate.query.plan_cache_max_size", "32"));
        result.setProperty("hibernate.query.plan_parameter_metadata_max_size", jpaConfig.getProperty("hibernate.query.plan_parameter_metadata_max_size", "16"));
        result.setProperty("hibernate.query.in_clause_parameter_padding", jpaConfig.getProperty("hibernate.query.in_clause_parameter_padding", "false"));
        String physicalNamingStrategy = this.applicationContext.getEnvironment().getProperty("spring.jpa.hibernate.naming.physical-strategy");
        if (StringUtils.hasText(physicalNamingStrategy)) {
            result.setProperty("hibernate.physical_naming_strategy", physicalNamingStrategy);
        }

        String implicitNamingStrategy = this.applicationContext.getEnvironment().getProperty("spring.jpa.hibernate.naming.implicit-strategy");
        if (StringUtils.hasText(implicitNamingStrategy)) {
            result.setProperty("hibernate.implicit_naming_strategy", implicitNamingStrategy);
        }
        result.setProperty("hibernate.allow_update_outside_transaction", "true");

        return result;
    }

    private String getDefaultHibernateDialect(String jdbcUrl) {
        if (jdbcUrl.contains("jdbc:oracle:")) {
            // return Oracle12cDialect.class.getName();
        } else if (jdbcUrl.contains("jdbc:ingres:")) {
            // return Ingres10Dialect.class.getName();
        } else if (!jdbcUrl.contains("jdbc:mysql:") && !jdbcUrl.contains("jdbc:mariadb:")) {
            if (this.isSybase(jdbcUrl)) {
//                return SynnexSybaseDialect.class.getName();
                return "not support";
            } else if (jdbcUrl.contains("jdbc:h2:")) {
                return H2Dialect.class.getName();
            } else {
                return !jdbcUrl.contains("jdbc:ch:") && !jdbcUrl.contains("jdbc:clickhouse:") ? null : MySQLDialect.class.getName();
            }
        } else {
            return MySQLDialect.class.getName();
        }
        return null;
    }

    private boolean isSybase(String jdbcUrl) {
        return jdbcUrl.contains("jdbc:sybase:");
    }

    private boolean isKeyGenSupport(String jdbcUrl) {
        return !jdbcUrl.contains("jdbc:oracle:") && !jdbcUrl.contains("jdbc:ingres:");
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
        this.env = applicationContext.getEnvironment();
    }
}
